//! request http
//!
//! include scan and view

use crate::task::Task;
use crate::utils::ThrowError;
use base_url::Url;
use console::{style, StyledObject};
use std::path::PathBuf;

#[allow(unused_imports)]
use prettytable::{cell, row, Cell, Row, Table};

#[cfg(feature = "bin")]
use progressing::{mapping, Baring};
/// http scanner
#[derive(Debug)]
pub struct Controller<'a> {
    url: &'a str,
    file: &'a Option<PathBuf>,
    config: &'a crate::cli::Config,
}

/// http response
#[derive(Debug, Clone)]
pub enum StatusCode {
    Fail(String),
    OK(usize),
    Error(usize),
}

impl From<u16> for StatusCode {
    fn from(e: u16) -> Self {
        Self::from(e as usize)
    }
}

#[allow(dead_code)]
impl StatusCode {
    /// is ok status
    pub fn is_ok(&self) -> bool {
        match self {
            Self::OK(_) => true,
            _ => false,
        }
    }
    /// console status
    pub fn print(&self) -> StyledObject<String> {
        match self {
            Self::OK(_) => style(format!("{:?}", self)).cyan(),
            Self::Fail(_) => style(format!("{:?}", self)).red(),
            Self::Error(_) => style(format!("{:?}", self)).red(),
        }
    }
}

impl From<usize> for StatusCode {
    fn from(e: usize) -> Self {
        if e < 400 {
            return Self::OK(e);
        }

        Self::Error(e)
    }
}

impl<'a, 'b> From<&'b Task<'a>> for Controller<'b>
where
    'a: 'b,
{
    fn from(t: &'b Task) -> Self {
        if let Err(e) = Url::parse(&t.config.url) {
            crate::errorln!(
                "URL[{}] parsing failed for {}",
                style(&t.config.url).cyan(),
                style(e.to_string()).red(),
            );
            std::process::exit(-1);
        }

        Self {
            config: &t.config,
            url: &t.config.url,
            file: &t.config.file,
        }
    }
}

impl<'a> Controller<'a> {
    /// print scanning result
    pub fn scan(&self) -> Vec<(String, StatusCode)> {
        #[cfg(feature = "bin")]
        {
            crate::waitln!("SCANNING", &self.url);
        }

        let addr_list: Vec<String> = self
            .file
            .as_ref()
            .map_or(include_str!("address.txt").to_string(), |s| {
                std::fs::read_to_string(s)
                    .debug_error(self.config.debug)
                    .throw_then(|_| {
                        crate::errorln!(
                            "Can not read file from [{}]",
                            style(s.to_str().unwrap()).cyan().bold()
                        )
                    })
            })
            .split("\n")
            .filter(|s| s.len() > 0)
            .map(|s| s.trim_end().to_owned())
            .collect();
        let mut res_list = vec![];

        #[cfg(feature = "bin")]
        let mut table = Table::new();

        #[cfg(feature = "bin")]
        let stdout = console::Term::stdout();

        #[cfg(feature = "bin")]
        let mut bar = mapping::Bar::with_range(0, addr_list.len());

        for addr in &addr_list {
            let url = if self.config.join || self.url.ends_with("/") {
                format!("{}{}", self.url, addr)
            } else {
                format!("{}/{}", self.url, addr)
            };

            let res = Self::http(&url);

            res_list.push((url, res));

            #[cfg(feature = "bin")]
            {
                bar.add(1usize);
                stdout.clear_last_lines(1).unwrap();
                println!("{}", bar);
            }
        }

        #[cfg(feature = "bin")]
        {
            stdout.clear_last_lines(1).unwrap();
            table.add_row(row![
                style("URL").bold().blink(),
                style("BAD STATUS").bold().blink(),
            ]);

            for (url, code) in &res_list {
                if !code.is_ok() {
                    let row = row![style(&url), code.print()];
                    table.add_row(row);
                }
            }

            table.add_row(row!["", ""]);
            table.add_row(row![style("URL").bold(), style("GOOD STATUS").bold()]);

            for (url, code) in &res_list {
                if code.is_ok() {
                    let row = row![style(&url), code.print()];
                    table.add_row(row);
                }
            }

            table.printstd();
        }

        res_list
    }

    /// get response html code
    pub fn view(&self) -> String {
        #[cfg(feature = "bin")]
        {
            crate::waitln!("VIEWING", &self.url);
        }
        let vec = attohttpc::get(&self.url)
            .send()
            .debug_error(self.config.debug)
            .and_then(|res| res.bytes())
            .debug_error(self.config.debug)
            .throw_err("Can not receive the response data");

        String::from_utf8(vec)
            .debug_error(self.config.debug)
            .throw_err("Can not transform bytes into console string")
    }

    /// check one url only
    pub fn ping(&self) -> StatusCode {
        let res = Self::http(&self.url);
        #[cfg(feature = "bin")]
        {
            crate::waitln!("PING", self.url);
            let mut table = Table::new();
            table.add_row(row![
                style("URL").bold().blink(),
                style("STATUS").bold().blink(),
            ]);
            table.add_row(row![style(&self.url), res.print()]);
            table.printstd();
        }
        res
    }

    /// return a http status result
    fn http(url: &str) -> StatusCode {
        let res = attohttpc::get(url).send();
        match res {
            Err(e) => StatusCode::Fail(e.to_string()),
            Ok(r) => StatusCode::from(r.status().as_u16()),
        }
    }
}
